/**
* Name: Ex L11b_flocking_interaction
* Author: Gudrun WALLENTIN
* Tags: agent movement, agent-agent interaction, flocking
* Description: model in the "Spatial Simulation" module of the UNIGIS curriculum 
*/

model ExL11b_flocking_interaction

global torus: true {

	//  parameters
	int number_of_cows <- 25 min: 3 max: 60; 	
	float min_separation <- 3.0  min: 0.1  max: 10.0 ;
	int max_separate_turn <- 4 min: 0 max: 20;
	int max_cohere_turn <- 4 min: 0 max: 20;
	int max_align_turn <- 20 min: 0 max: 20;
	float max_random_turn <- 2.0 min: 0.0 max: 20.0;
	float vision <- 12.0  min: 0.0  max: 70.0 ;	
	int vision_angle <- 230  min: 160  max: 340 ;	
	float step_length <- 5.0  min: 0.0  max: 20.0 ;	
	geometry shape <- square(100);
			
	// initialise model
	init {
		// create and distribute cows
		create cows number:number_of_cows {
			action_neighbourhood <- arc(step_length, heading, 2*20);
		}
		
	}		
} 
	

//  cows agents
species cows skills: [ moving ] {

	// cows attributes
	rgb colour <- #black;

	// flocking variables
    list<cows> flockmates <- [];	    
    cows nearest_neighbour;	
    float avg_head;
    float avg_twds_mates ;
	geometry perception_neighbourhood;    
	geometry next_action_neighbourhood;
	geometry action_neighbourhood;
	point my_destination;	
	
//***** ***** ***** ***** ***** // perceive // ***** ***** ***** ***** ***** //
	
	reflex perceive {
		//perceive within an arc with the vision radius, the heading, and the width of the vision's angle.
		perception_neighbourhood <- arc(vision, heading, vision_angle);
		// search for flockmates
		do find_flockmates ;	
		if (not empty (flockmates)) {
			do find_nearest_neighbour;
		}	
	}	
	
//***** ***** ***** ***** ***** // decide // ***** ***** ***** ***** ***** //

	// make decisions according to the behavioural blueprint for flocking
	reflex decide {

		// if there are other agents in vision... 
		if (not empty (flockmates)) {
			// and my nearest neighbour is too close: turn away
			if (distance_to (self, nearest_neighbour) < min_separation) {
				do separate;
				action_neighbourhood <- arc(step_length, heading, max_separate_turn);
			}
			// otherwise align and cohere
			else {
				do align;
				do cohere;
				action_neighbourhood <- arc(step_length, heading, max_align_turn + max_cohere_turn);
			}
		}
		// if there are no other agents in vision: turn a bit to prepare random walk
		else {
			do prepare_random_walk;
			action_neighbourhood <- arc(step_length, heading, max_random_turn);
		}
	}			
	
//***** ***** ***** ***** ***** // act // ***** ***** ***** ***** ***** //
	
	reflex act {
		// ..and move in the decided direction
		do move speed: step_length;
	}	
	
	
//***** ***** ***** ***** ***** // helper procedures // ***** ***** ***** ***** ***** //
	
	//flockmates are defined spatially, within a buffer of vision
	action find_flockmates {
        flockmates <- ((cows overlapping perception_neighbourhood) - self);
	}
		
		//find nearest neighbour
	action find_nearest_neighbour {
        nearest_neighbour <- flockmates with_min_of(distance_to (self.location, each.location)); 
	}		
	
    // separate from the nearest neighbour of flockmates
    action separate  {
    	do turn_away (nearest_neighbour towards self, max_separate_turn);
    }

    //Reflex to align the boid with the other cows in the range
    action align  {
    	avg_head <- avg_mate_heading () ;
        do turn_towards (avg_head, max_align_turn);
    }

    //Reflex to apply the cohesion of the cows group in the range of the agent
    action cohere  {
		avg_twds_mates <- avg_heading_towards_mates ();
		do turn_towards (avg_twds_mates, max_cohere_turn); 
    }
    
    //compute the mean vector of headings of my flockmates
    float avg_mate_heading {
	    ask flockmates {
	    	my_destination <- {location.x + cos(heading), location.y + sin(heading)};
	    }    	
		list<cows> flockmates_insideShape <- flockmates where (each.my_destination != nil);
		float x_component <- sum (flockmates_insideShape collect (each.my_destination.x - each.location.x));
		float y_component <- sum (flockmates_insideShape collect (each.my_destination.y - each.location.y));
		//if the flockmates vector is null, return my own, current heading
		if (x_component = 0 and y_component = 0) {
			return heading;
		} 
		//else compute average heading of vector  		
		else {
			// note: 0-heading direction in GAMA is east instead of north! -> thus +90
			return -1 * atan2 (x_component, y_component) + 90;
		}	
    }  

    //compute the mean direction from me towards flockmates	    
    float avg_heading_towards_mates {
    	float x_component <- mean (flockmates collect (cos (towards(self.location, each.location))));
    	float y_component <- mean (flockmates collect (sin (towards(self.location, each.location))));
    	//if the flockmates vector is null, return my own, current heading
    	if (x_component = 0 and y_component = 0) {
    		return heading;
    	}
		//else compute average direction towards flockmates
    	else {
    		// note: 0-heading direction in GAMA is east instead of north! -> thus +90
    		return -1 * atan2 (x_component, y_component) + 90;	
    	}
    } 	    
    
    // cohere
    action turn_towards (float new_heading, int max_turn) {
		float subtract_headings <- new_heading - heading;
		if (subtract_headings < -180) {subtract_headings <- subtract_headings + 360;}
		if (subtract_headings > 180) {subtract_headings <- subtract_headings - 360;}
    	do turn_at_most ((subtract_headings), max_turn);
    }

	// separate
    action turn_away (float new_heading, int max_turn) {
		float subtract_headings <- heading - new_heading;
		if (subtract_headings < -180) {subtract_headings <- subtract_headings + 360;}
		if (subtract_headings > 180) {subtract_headings <- subtract_headings - 360;}
    	do turn_at_most ((-1 * subtract_headings), max_turn);
    }
    
    // align
    action turn_at_most (float turn, int max_turn) {
    	if abs (turn) > max_turn {
    		if turn > 0 {
    			//right turn
    			heading <- heading + max_turn;
    		}
    		else {
    			//left turn
    			heading <- heading - max_turn;
    		}
    	}
    	else {
    		heading <- heading + turn;
    	} 
    }
    
    //turn 2 degrees maximum
    action prepare_random_walk {
    	do turn_at_most (rnd(max_random_turn), int(max_random_turn));
    }
    
    
//***** ***** ***** ***** ***** // visualisation // ***** ***** ***** ***** ***** //
    
    // cows visualisation settings
	aspect default {
		//location at the end of the time step
 		draw circle(1) color: colour;
	}

	// default arrow
	aspect arrow {
		// arrow of this time step's movement
 		draw line([{location.x - step_length * cos(heading), location.y - step_length * sin(heading)}, location]) end_arrow: 1 color: #orange;
	}
	
	// visualise perception neighbourhood 
	aspect perception_area {
    	draw perception_neighbourhood color: #red ;
	}
	
	// visualise action neighbourhood 
	aspect action_area {
		//update action neighbourhood with the state at the end of the time step to sync viz with cow location
		action_neighbourhood <- arc(step_length, heading, 2*20);		
		//potential area to where it can move next time step
    	draw action_neighbourhood color: #magenta ;
	}	
}

// simulation settings
experiment simulation type:gui {
	
	//user defined parameters
	parameter 'Iniatial number of cows' var: number_of_cows;
	parameter 'Max cohesion turn' var: max_cohere_turn ;
	parameter 'Max alignment turn' var:  max_align_turn; 
	parameter 'Max separation turn' var: max_separate_turn;
	parameter 'Max random turn' var: max_random_turn ;
	parameter 'Minimal cow distance'  var: min_separation;
	parameter 'Vision distance' var: vision;
	parameter 'Vision angle' var: vision_angle ;	
	parameter 'Speed' var: step_length ;	
	 
	output {	
		// map
		display map  {
			//species cows aspect: perception_area transparency:0.9;
			species cows aspect: action_area transparency:0.6;
			species cows aspect: default;
			species cows aspect: arrow;	
	
		}
	}
}
